/*
 * $Id$
 *
 * Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
package org.jdesktop.swingx.plaf.synth;

import java.awt.Graphics;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;

import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.synth.ColorType;
import javax.swing.plaf.synth.Region;
import javax.swing.plaf.synth.SynthConstants;
import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthLookAndFeel;
import javax.swing.plaf.synth.SynthPainter;
import javax.swing.plaf.synth.SynthStyle;

/**
 * Utility class as stand-in for package private synth utility methods.
 * 
 * @author Jeanette Winzenburg
 */
public class SynthUtils {

	// ----------------------- context-related

	/**
	 * Used to avoid null painter checks everywhere.
	 */
	private static SynthPainter NULL_PAINTER = new SynthPainter() {
	};

	/**
	 * Returns a SynthContext with the specified values.
	 *
	 * @param component
	 *            JComponent
	 * @param region
	 *            Identifies the portion of the JComponent
	 * @param style
	 *            Style associated with the component
	 * @param state
	 *            State of the component as defined in SynthConstants.
	 * @return a SynthContext with the specified values.
	 * 
	 * @throws NullPointerException
	 *             if component, region of style is null.
	 * 
	 */
	public static SynthContext getContext(JComponent c, Region region, SynthStyle style, int state) {
		return new SynthContext(c, region, style, state);
	}

	/**
	 * @param context
	 * @param style
	 * @return
	 */
	public static SynthContext getContext(SynthContext context, SynthStyle style) {
		if (context.getStyle().equals(style))
			return context;
		return getContext(context.getComponent(), context.getRegion(), style, context.getComponentState());
	}

	/**
	 * Returns a context with the given component state and all other fields
	 * same as input context.
	 * 
	 * @param context
	 *            the context, must not be null
	 * @param state
	 *            the component state.
	 * @return a context with the given component state and other fields as inpu
	 *         context.
	 */
	public static SynthContext getContext(SynthContext context, int state) {
		if (context.getComponentState() == state)
			return context;
		return getContext(context.getComponent(), context.getRegion(), context.getStyle(), state);
	}

	/**
	 * Returns a SynthPainter from the context's style. Fall-back to default if
	 * none available.
	 * 
	 * @param context
	 *            SynthContext containing the style, must not be null.
	 * @return a SynthPainter from the context's style, or a default if null.
	 */
	public static SynthPainter getPainter(SynthContext context) {
		SynthPainter painter = context.getStyle().getPainter(context);
		return painter != null ? painter : NULL_PAINTER;
	}

	// ------------------- style-related

	/**
	 * Returns true if the Style should be updated in response to the specified
	 * PropertyChangeEvent. This forwards to
	 * <code>shouldUpdateStyleOnAncestorChanged</code> as necessary.
	 */
	public static boolean shouldUpdateStyle(PropertyChangeEvent event) {
		String eName = event.getPropertyName();
		if ("name" == eName) {
			// Always update on a name change
			return true;
		} else if ("componentOrientation" == eName) {
			// Always update on a component orientation change
			return true;
		} else if ("ancestor" == eName && event.getNewValue() != null) {
			// Only update on an ancestor change when getting a valid
			// parent and the LookAndFeel wants this.
			LookAndFeel laf = UIManager.getLookAndFeel();
			return (laf instanceof SynthLookAndFeel && ((SynthLookAndFeel) laf).shouldUpdateStyleOnAncestorChanged());
		}
		// Note: The following two nimbus based overrides should be refactored
		// to be in the Nimbus LAF. Due to constraints in an update release,
		// we couldn't actually provide the public API necessary to allow
		// NimbusLookAndFeel (a subclass of SynthLookAndFeel) to provide its
		// own rules for shouldUpdateStyle.
		else if ("Nimbus.Overrides" == eName) {
			// Always update when the Nimbus.Overrides client property has
			// been changed
			return true;
		} else if ("Nimbus.Overrides.InheritDefaults" == eName) {
			// Always update when the Nimbus.Overrides.InheritDefaults
			// client property has changed
			return true;
		} else if ("JComponent.sizeVariant" == eName) {
			// Always update when the JComponent.sizeVariant
			// client property has changed
			return true;
		}
		return false;
	}

	// --------------- component related

	public static int getComponentState(JComponent c) {
		if (c.isEnabled()) {
			if (c.isFocusOwner()) {
				return SynthConstants.ENABLED | SynthConstants.FOCUSED;
			}
			return SynthConstants.ENABLED;
		}
		return SynthConstants.DISABLED;
	}

	// ---------------- divers ...

	/**
	 * A convenience method that handles painting of the background. All SynthUI
	 * implementations should override update and invoke this method.
	 * 
	 * @param context
	 *            must not be null
	 * @param g
	 *            must not be null
	 */
	public static void update(SynthContext context, Graphics g) {
		update(context, g, null);
	}

	/**
	 * A convenience method that handles painting of the background. All SynthUI
	 * implementations should override update and invoke this method.
	 * 
	 * @param context
	 *            must not be null
	 * @param g
	 *            must not be null
	 * @param the
	 *            bounds to fill, may be null to indicate the complete size
	 */
	public static void update(SynthContext context, Graphics g, Rectangle bounds) {
		JComponent c = context.getComponent();
		SynthStyle style = context.getStyle();
		int x, y, width, height;

		if (bounds == null) {
			x = 0;
			y = 0;
			width = c.getWidth();
			height = c.getHeight();
		} else {
			x = bounds.x;
			y = bounds.y;
			width = bounds.width;
			height = bounds.height;
		}

		// Fill in the background, if necessary.
		boolean subregion = context.getRegion().isSubregion();
		if ((subregion && style.isOpaque(context)) || (!subregion && c.isOpaque())) {
			g.setColor(style.getColor(context, ColorType.BACKGROUND));
			g.fillRect(x, y, width, height);
		}
	}

}
